想象一个场景,我们外出去餐馆吃饭。去之前,我们一般会根据以往的就餐经历、朋友介绍或网络推荐,形成对各个餐馆的预期,从而确定具体去哪里吃。等到就餐的时候,我们会亲身体验这个餐馆饭菜的口味、餐馆环境和服务,对餐馆质量产生实际的体验感受。这时候,我们会不自觉地将实际的体验感受和就餐之前对餐馆的预期进行对比,如果实际体验高于预期,我们就会觉得这餐馆很不错,下一次可能还会再来消费,或者推荐给朋友,即对餐馆产生的忠诚度;如果实际体验下来发现没有想象中的那么好,我们就会感到不满意,可能会反手给它个差评,下次也就不会再选择这家餐馆了。

这个就餐的体验过程,可以用逻辑图表达成以下的关联:

可能有些小伙伴对这个逻辑图比较熟悉了,没错,这就是经典的顾客满意度评估模型的核心框架。图中,单箭头表示原因变量对结果变量有直接影响关系。

从图示中可以知道,我们对餐馆的满意度,受到心理预期和餐馆质量的直接影响。实际上,所谓满意度,就是指一个人对产品/服务的实际体验效果与他的期望比较后所产生的心理反应。期望与实际体验比较后,会形成对餐馆的满意度,而满意度又会直接影响我们后续是会投诉,还是会重复消费或者推荐给他人,即顾客忠诚度。

图1清晰地展示了餐馆质量、顾客期望、满意度、顾客投诉和顾客忠诚度之间的因果关系。但这里的问题在于,这些因素都是我们从就餐体验过程中抽象出来的概念,是无法直接测量出来的。想要量化各个因素,就需要设定针对每个维度的测量指标来反映这些抽象概念。考虑这些测量指标,再对变量稍微调整下,我们就可以得到以下的逻辑图,矩形中的变量为用以测量抽象概念的指标。

以上即是一个完整的顾客满意度评估模型,而满意度评估模型正是结构方程模型的典型应用。图2这样的逻辑图,则是结构方程模型的经典表达形式,称为路径图。通过路径图,我们可以清晰直观地描述出测量指标与影响变量之间、以及影响变量之间的结构关系。

概括来说,结构方程模型(Structural Equation Modeling,简称为SEM)是一种基于观察变量的协方差矩阵,对潜在变量之间的因果关系模型进行估计和检验的高级统计分析方法。

结构方程模型的变量¶

根据是否能被直接测量或观察,结构方程模型的变量可分为观察变量和潜在变量。

观察变量(又称为测量变量、外显变量、测量指标或指标变量),是指可以直接观察、测量获得量化数据的测量指标,如饭菜口味、环境舒适、服务周到等这些指标,都属于观察变量。在结构方程模型路径图中,观察变量以矩形来代表。

而像餐馆质量、预期、满意度和忠诚度,这些无法直接测量或观察、需要通过观察变量间接测量的抽象概念,则称为潜在变量(latent variable),又称为非观察变量(unobserved variables)、建构变量(construct variable)。要对餐馆质量作出评价,就需要通过饭菜口味是否可口、餐馆环境是否舒适、服务是否周到等具体指标来认识,由这些可以直接观察测量的观察变量来反映,因此观察变量又被称为潜在变量的测量指标(或观察标识变量)。在结构方程模型路径图中,潜在变量用椭圆形来代表。

结构方程模型分析的重点是潜在变量,其目的是要估计并检验假设模型中的潜在变量间的因果关系。但潜在变量无法直接测量或观察,需要通过观察变量来间接获取测量数据,因此,一个完整的结构方程模型会包括两部分:(1)联系观察变量和潜在变量的测量模型(measurement model);(2)表示潜在变量间的因果关系的结构模型(structural model)。

测量模型描述的是观察变量和潜在变量之间的回归关系,目的是要检验观察变量作为潜变量的测量手段是否合适,这个过程由验证性因子分析来完成。图3中红框的内容均属于测量模型。

从变量间的因果关系出发,潜在变量又可分为内生变量和外生变量。

内生变量(或称为内因潜在变量、内衍潜在变量,endogenous variable),指在模型中被其他变量影响的变量,在因果关系中作为结果,即所谓的因变量,也就是路径图中单箭头指向的变量,如图4中的餐馆质量、满意度和忠诚度,都属于内生潜变量,其中餐馆质量和满意度在结构模型中,既是原因变量,也是结果变量。

外生变量(或称为外因变量、外衍潜在变量,exogenous variable),指在模型中不受其他变量影响、但会影响其他变量的潜在变量,在因果关系中作为原因,即所谓的自变量,也就是路径图中不被任何变量以单箭头指向的变量(不被单箭头指向,但被双箭头指向的变量也算是外生变量),如图4中的顾客预期。外生变量的影响因素在模型之外。

在结构方程模型中,观察变量和潜在变量都会伴随一定的误差。

我们知道,潜在变量只是一种抽象构念,需要通过观察变量进行测量估计出来。但单一的观察变量是难以反映出潜在变量的抽象内容的,必须通过两个以上的观察变量进行推估。一般来说,观察变量越多,就越能正确地反映潜在变量的内涵,可信度也就会越高。但这里必然会伴随的问题是,潜在变量不可能被多个观察变量完全反映,即这些观察变量的变异无法被共同的潜在变量充分解释,而未被解释的部分,即是观察变量的误差变异项,称为测量误差。下图5中小椭圆内的项目(e1-e11)即是测量误差项。

在结构模型中,内生潜变量除了可以被模型中的外生潜变量解释,还会受到模型外的其他因素的影响,而这些无法被外生潜变量解释的变异部分,即是内生潜在变量的误差变异项,也称为干扰项(disturbance)、解释残差或预测残差,相当于传统回归模型中的(1-R^2)。

结构方程模型中的参数¶

参数和变量一样,也是统计学中最基本的重要概念,指的是一种需要被估计的未知数值,比如因子分析中的因子载荷、回归模型中的回归系数。

在结构方程模型中,根据是否有估计的需求,将参数分为三类:自由参数、固定参数、限定参数。图6中标明了模型的各参数。

自由参数(free parameter):指需要被估计的参数,比如潜变量之间的路径系数、观察变量和潜变量之间的因子载荷、误差变异项的方差。在结构模型中,潜变量之间的影响效应以路径系数来表示,反映了外生潜变量(原因变量)对内生变量(结果变量)的影响程度,相当于回归分析中的回归系数,如图6中的w5-w8。而潜在变量对观察变量的影响大小,以因子载荷来表示,反映作为测量指标的观察变量对潜在变量的重要程度,如图6中的w9-w15。图6中v1-v13表示误差变异项的方差(variances)。

固定参数(fixed parameter):指不需要被估计的参数,通常这些参数会被设定为某个常数(通常是0或者1),比如图6中,每个测量模型都将潜在变量所影响的任一观察变量与该潜变量间的因子载荷固定为常数1 ,此时该因子载荷即是一个固定参数。

限定参数(constrained parameter):指可被设定为与其他参数相同值的参数,比如B样本的某一个参数被认为与A样本的其中一个参数等值,那么这个参数就是限定参数。

结构方程模型分析的目的,即是要估计出假设模型中的自由参数,从而明确变量之间的影响关系是否存在以及它们的影响程度是怎样的。

结构方程模型有啥用¶

结构方程模型的应用研究,主要集中于以下几个方面:

  1. 影响因素分析

通过结构方程模型,探究某个问题/主题的影响因素,比如研究影响主观幸福感的主要因素。

  1. 评价指标体系研究

通过结构方程模型,找出某个问题/问题的影响因素及各因素之间的关系之后,可构建评价指标体系,以实现对该问题进行跟踪衡量。

  1. 满意度评估

通过结构方程模型,可进行顾客满意度、员工满意度、工作满意度等的评估研究,探寻影响满意度的因素,从而搭建满意度评估模型,持续监测满意度现状,从而更能针对性地制定发展计划和改进措施。

  1. 竞争力评价分析

通过结构方程模型,构建地区或者个人的竞争力评价模型,以对竞争力进行定量评价。

结构方程模型的建模过程主要有五个步骤:

  1. 模型设定 结构方程模型是一种验证性的分析方法,用以检验一个假设模型是否合理,而不是用来寻找和发现一种合适的模型。因此,应用结构方程模型的首要步骤是要基于理论或以往研究成果来提出一个待检验的初始假设模型,并借由结构方程模型分析软件的语言来表达出来。这个过程这就是模型设定。 表达假设模型的方法有多种,其中最常用也是最简单、直接的方法是以路径图来描绘出观察变量与潜变量之间(即测量模型)以及潜变量之间的关系(即结构模型)。如前面举例的餐馆满意度模型,即是以路径图来表达设定好的假设模型。
  2. 模型识别 在设定结构方程模型时,需要考虑该假设模型是否可以被识别。模型识别要解决的问题是,假设模型中设定的每一个未知参数(即自由参数)是否可以基于观察数据求出唯一解。如果模型不可识别,那就意味着模型中的所有参数都无法被估计。
  3. 模型估计 设定好模型后,接下来要根据观察变量的方差和协方差进行参数估计。结构方程模型的估计逻辑不同于回归分析方法,它的目标不是尽可能地缩小预测值与观测值之间的差异,而是要尽可能地缩小观测的方差/协方差矩阵(基于样本数据)与预测的方差协方差矩阵(由模型推导预测)之间的差异。这个差异量数即是残差(residuals)矩阵。常用的模型估计方法主要是最大似然法(maximum likelihood,简称为ML)和广义最小二乘法(generalized least squares,简称GLS)。
  4. 模型评价 完成模型估计后,接下来要做的是对模型进行评估和检验,以判断研究者提出的假设模型是否能够用来描述实际观察到的变量关系,这个评估检验的过程,称为模型拟合评价(model-fit-evaluation)。
  5. 模型修正 如果模型拟合度不理想,模型不符合观测数据,就需要对模型进行调整修正,然后再用同一观测数据重新估计和检验。模型的修正并不是随意的,而是要基于观测数据、理论研究、实际意义来综合考虑,而不能只追求统计上的正确,因为结构方程模型分析的目标是不仅要在统计上得到一个拟合度理想的模型,更要让每一个参数都能有符合实际的解释。

In [1]:
from semopy import Model
from semopy.examples import holzinger39
import semopy

desc = holzinger39.get_model()
data = holzinger39.get_data()
print(desc)
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9
In [2]:
data
Out[2]:
id sex ageyr agemo school grade x1 x2 x3 x4 x5 x6 x7 x8 x9
1 1 1 13 1 Pasteur 7.0 3.333333 7.75 0.375 2.333333 5.75 1.285714 3.391304 5.75 6.361111
2 2 2 13 7 Pasteur 7.0 5.333333 5.25 2.125 1.666667 3.00 1.285714 3.782609 6.25 7.916667
3 3 2 13 1 Pasteur 7.0 4.500000 5.25 1.875 1.000000 1.75 0.428571 3.260870 3.90 4.416667
4 4 1 13 2 Pasteur 7.0 5.333333 7.75 3.000 2.666667 4.50 2.428571 3.000000 5.30 4.861111
5 5 2 12 2 Pasteur 7.0 4.833333 4.75 0.875 2.666667 4.00 2.571429 3.695652 6.30 5.916667
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
297 346 1 13 5 Grant-White 8.0 4.000000 7.00 1.375 2.666667 4.25 1.000000 5.086957 5.60 5.250000
298 347 2 14 10 Grant-White 8.0 3.000000 6.00 1.625 2.333333 4.00 1.000000 4.608696 6.05 6.083333
299 348 2 14 3 Grant-White 8.0 4.666667 5.50 1.875 3.666667 5.75 4.285714 4.000000 6.00 7.611111
300 349 1 14 2 Grant-White 8.0 4.333333 6.75 0.500 3.666667 4.50 2.000000 5.086957 6.20 4.388889
301 351 1 13 5 Grant-White NaN 4.333333 6.00 3.375 3.666667 5.75 3.142857 4.086957 6.95 5.166667

301 rows × 15 columns

In [3]:
desc
Out[3]:
'visual =~ x1 + x2 + x3\ntextual =~ x4 + x5 + x6\nspeed =~ x7 + x8 + x9'
In [4]:
mod = Model(desc)
res_opt = mod.fit(data)
estimates = mod.inspect()
In [5]:
res_opt
Out[5]:
SolverResult(fun=0.28340773379855455, success=True, n_it=28, x=array([0.55442111, 0.730526  , 1.11307615, 0.92611994, 1.17997968,
       1.08251679, 0.84373074, 0.44620772, 0.79970762, 0.56580436,
       0.55016064, 0.48793446, 1.13339096, 0.35617088, 0.37111714,
       0.38337653, 0.98003411, 0.17360283, 0.80830954, 0.40827718,
       0.26213526]), message='Optimization terminated successfully', name_method='SLSQP', name_obj='MLW')
In [6]:
estimates
Out[6]:
lval op rval Estimate Std. Err z-value p-value
0 x1 ~ visual 1.000000 - - -
1 x2 ~ visual 0.554421 0.099727 5.559413 0.0
2 x3 ~ visual 0.730526 0.10918 6.691009 0.0
3 x4 ~ textual 1.000000 - - -
4 x5 ~ textual 1.113076 0.065392 17.021522 0.0
5 x6 ~ textual 0.926120 0.055425 16.709493 0.0
6 x7 ~ speed 1.000000 - - -
7 x8 ~ speed 1.179980 0.165045 7.149459 0.0
8 x9 ~ speed 1.082517 0.151354 7.152197 0.0
9 speed ~~ speed 0.383377 0.086171 4.449045 0.000009
10 textual ~~ textual 0.980034 0.112145 8.739002 0.0
11 textual ~~ speed 0.173603 0.049316 3.520223 0.000431
12 visual ~~ visual 0.808310 0.145287 5.563548 0.0
13 visual ~~ textual 0.408277 0.073527 5.55273 0.0
14 visual ~~ speed 0.262135 0.056252 4.659977 0.000003
15 x3 ~~ x3 0.843731 0.090625 9.31016 0.0
16 x5 ~~ x5 0.446208 0.058387 7.642264 0.0
17 x7 ~~ x7 0.799708 0.081387 9.825966 0.0
18 x9 ~~ x9 0.565804 0.070757 7.996483 0.0
19 x1 ~~ x1 0.550161 0.113439 4.84983 0.000001
20 x8 ~~ x8 0.487934 0.074167 6.578856 0.0
21 x2 ~~ x2 1.133391 0.101711 11.143202 0.0
22 x6 ~~ x6 0.356171 0.04303 8.277334 0.0
23 x4 ~~ x4 0.371117 0.047712 7.778264 0.0
In [7]:
mod.predict(data)
Out[7]:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 3.333333 7.75 0.375 2.333333 5.75 1.285714 3.391304 5.75 6.361111
2 5.333333 5.25 2.125 1.666667 3.00 1.285714 3.782609 6.25 7.916667
3 4.500000 5.25 1.875 1.000000 1.75 0.428571 3.260870 3.90 4.416667
4 5.333333 7.75 3.000 2.666667 4.50 2.428571 3.000000 5.30 4.861111
5 4.833333 4.75 0.875 2.666667 4.00 2.571429 3.695652 6.30 5.916667
... ... ... ... ... ... ... ... ... ...
297 4.000000 7.00 1.375 2.666667 4.25 1.000000 5.086957 5.60 5.250000
298 3.000000 6.00 1.625 2.333333 4.00 1.000000 4.608696 6.05 6.083333
299 4.666667 5.50 1.875 3.666667 5.75 4.285714 4.000000 6.00 7.611111
300 4.333333 6.75 0.500 3.666667 4.50 2.000000 5.086957 6.20 4.388889
301 4.333333 6.00 3.375 3.666667 5.75 3.142857 4.086957 6.95 5.166667

301 rows × 9 columns

In [8]:
factors = mod.predict_factors(data)
In [9]:
factors
Out[9]:
speed textual visual
0 0.061716 -0.137501 -0.816223
1 0.625629 -1.012812 0.049137
2 -0.840137 -1.872462 -0.761486
3 -0.271071 0.018507 0.419352
4 0.194107 -0.122328 -0.416404
... ... ... ...
296 0.050066 -0.507345 -0.494133
297 0.218031 -0.698790 -0.938102
298 0.569395 1.169099 0.037183
299 0.044106 0.138690 -0.470771
300 0.337366 0.848173 0.148833

301 rows × 3 columns

In [10]:
#g = semopy.semplot(mod, "pd.png")
In [11]:
#semopy.report(mod, "Political Democracy")